﻿namespace SecutityLib
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class HillCipher : EncodingDecodingTech
    {
        /// <summary>
        /// Eclids the inverse.
        /// </summary>
        /// <param name="theNumber">The A.</param>
        /// <param name="theBase">The B.</param>
        /// <returns>eculid's inverse</returns>
        public static int EclidInverse(int theNumber, int theBase)
        {
            int q = 0;
            int a1 = 1;
            int a2 = 0;
            int a3 = theBase;
            int b1 = 0;
            int b2 = 1;
            int b3 = theNumber;

            int t1 = 0;
            int t2 = 0;
            int t3 = 0;

            while (true)
            {
                if (b3 == 1)
                {
                    return b2;
                }

                if (b3 == 0)
                {
                    return 0;
                }

                q = a3 / b3;
                t1 = b1;
                t2 = b2;
                t3 = b3;
                b1 = a1 - (b1 * q);
                b2 = a2 - (b2 * q);
                b3 = a3 % b3;
                a1 = t1;
                a2 = t2;
                a3 = t3;
            }
        }

        /// <summary>
        /// Encodes the specified plain text.
        /// </summary>
        /// <param name="plainText">The plain text.</param>
        /// <param name="key">The key.</param>
        /// <returns>cipher text</returns>
        public override string Encode(string plainText, string key)
        {
            if (this.CheckText(plainText))
            {
                string cipher = string.Empty;
                int[] plainmatrix = this.GetText(plainText);
                int[,] keyMatrix = this.GetKey(key);
                int keyLength = keyMatrix.GetLength(0);
                bool plainDone = false;

                int cnt = 0;

                while (plainDone != true)
                {
                    int[,] plaintemp = new int[keyLength, 1];
                    for (int i = 0; i < keyLength; i++)
                    {
                        if (cnt == plainmatrix.Length)
                        {
                            plainDone = true;
                            plaintemp[i, 0] = 'x' - 'a';
                        }
                        else
                        {
                            plaintemp[i, 0] = plainmatrix[cnt];
                            cnt++;
                            if (cnt == plainmatrix.Length)
                            {
                                plainDone = true;
                            }
                        }
                    }

                    int[,] ciphertemp = MultiplyMat(keyMatrix, plaintemp);
                    for (int i = 0; i < ciphertemp.GetLength(0); i++)
                    {
                        for (int j = 0; j < ciphertemp.GetLength(1); j++)
                        {
                            cipher += (char)(ciphertemp[i, j] + 'a');
                        }
                    }
                }

                return cipher;
            }
            else
            {
                string cipher = string.Empty;
                int[] plainmatrix = this.GetNumbers(plainText);
                int[,] keyMatrix = this.GetKey(key);
                int keyLength = keyMatrix.GetLength(0);
                bool plainDone = false;

                int cnt = 0;

                while (plainDone != true)
                {
                    int[,] plaintemp = new int[keyLength, 1];
                    for (int i = 0; i < keyLength; i++)
                    {
                        if (cnt == plainmatrix.Length)
                        {
                            plainDone = true;
                            plaintemp[i, 0] = 'x' - 'a';
                        }
                        else
                        {
                            plaintemp[i, 0] = plainmatrix[cnt];
                            cnt++;
                            if (cnt == plainmatrix.Length)
                            {
                                plainDone = true;
                            }
                        }
                    }

                    int[,] ciphertemp = MultiplyMat(keyMatrix, plaintemp);
                    for (int i = 0; i < ciphertemp.GetLength(0); i++)
                    {
                        for (int j = 0; j < ciphertemp.GetLength(1); j++)
                        {
                            cipher += ciphertemp[i, j].ToString();
                            cipher += " , ";
                        }
                    }
                }

                return cipher;
            }
        }

        /// <summary>
        /// Decodes the specified plain text.
        /// </summary>
        /// <param name="cipherText">the cipher text</param>
        /// <param name="key">The key.</param>
        /// <returns>
        /// cipher text
        /// </returns>
        public override string Decode(string cipherText, string key)
        {
            if (this.CheckText(cipherText))
            {
                string plain = string.Empty;
                int[] ciphermatrix = this.GetText(cipherText);
                int[,] keyMatrix = this.GetKey(key);
                int[,] keyInv = GetKeyInverse(keyMatrix, keyMatrix.GetLength(0));

                if (keyInv == null)
                {
                    return string.Empty;
                }

                int keyLength = keyInv.GetLength(0);
                bool plainDone = false;

                int cnt = 0;

                while (plainDone != true)
                {
                    int[,] ciphertemp = new int[keyLength, 1];
                    for (int i = 0; i < keyLength; i++)
                    {
                        if (cnt == ciphermatrix.Length)
                        {
                            plainDone = true;
                            ciphertemp[i, 0] = 'x' - 'a';
                        }
                        else
                        {
                            ciphertemp[i, 0] = ciphermatrix[cnt];
                            cnt++;
                            if (cnt == ciphermatrix.Length)
                            {
                                plainDone = true;
                            }
                        }
                    }

                    int[,] plaintemp = MultiplyMat(keyInv, ciphertemp);
                    for (int i = 0; i < ciphertemp.GetLength(0); i++)
                    {
                        for (int j = 0; j < ciphertemp.GetLength(1); j++)
                        {
                            plain += (char)(plaintemp[i, j] + 'a');
                        }
                    }
                }

                return plain;
            }
            else
            {
                string plain = string.Empty;
                int[] ciphermatrix = this.GetNumbers(cipherText);
                int[,] keyMatrix = this.GetKey(key);
                int[,] keyInv = GetKeyInverse(keyMatrix, keyMatrix.GetLength(0));

                if (keyInv == null)
                {
                    return string.Empty;
                }

                int keyLength = keyInv.GetLength(0);
                bool plainDone = false;

                int cnt = 0;

                while (plainDone != true)
                {
                    int[,] ciphertemp = new int[keyLength, 1];
                    for (int i = 0; i < keyLength; i++)
                    {
                        if (cnt == ciphermatrix.Length)
                        {
                            plainDone = true;
                            ciphertemp[i, 0] = 'x' - 'a';
                        }
                        else
                        {
                            ciphertemp[i, 0] = ciphermatrix[cnt];
                            cnt++;
                            if (cnt == ciphermatrix.Length)
                            {
                                plainDone = true;
                            }
                        }
                    }

                    int[,] plaintemp = MultiplyMat(keyInv, ciphertemp);
                    for (int i = 0; i < ciphertemp.GetLength(0); i++)
                    {
                        for (int j = 0; j < ciphertemp.GetLength(1); j++)
                        {
                            plain += plaintemp[i, j].ToString();
                            plain += " , ";
                        }
                    }
                }

                return plain;
            }
        }

        /// <summary>
        /// Determs the specified a.
        /// </summary>
        /// <param name="a">matrix A</param>
        /// <param name="k">length of dimention of A</param>
        /// <returns>
        /// the determinant
        /// </returns>
        private static double Determ(int[,] a, int k)
        {
            double s = 1, det = 0;
            int[,] b = new int[a.GetLength(0), a.GetLength(0)];
            int i, j, m, n, c;

            if (k == 1)
            {
                return a[0, 0];
            }
            else
            {
                det = 0;
                for (c = 0; c < k; c++)
                {
                    m = 0;
                    n = 0;

                    for (i = 0; i < k; i++)
                    {
                        for (j = 0; j < k; j++)
                        {
                            b[i, j] = 0;

                            if (i != 0 && j != c)
                            {
                                b[m, n] = a[i, j];
                                if (n < k - 2)
                                {
                                    n++;
                                }
                                else
                                {
                                    n = 0;
                                    m++;
                                }
                            }
                        }
                    }

                    det = det + (s * (a[0, c] * Determ(b, k - 1)));
                    s = -1 * s;
                }
            }

            while (det < 0)
            {
                det += 26;
            }

            return det;
        }

        /// <summary>
        /// Cofacts the specified num.
        /// </summary>
        /// <param name="num">The num.</param>
        /// <param name="f">The f.</param>
        /// <returns>the cofactor of a matrix</returns>
        private static double[,] Cofact(int[,] num, int f)
        {
            int[,] b = new int[num.GetLength(0), num.GetLength(0)];
            double[,] fac = new double[num.GetLength(0), num.GetLength(0)];
            int p, q, m, n, i, j;

            for (q = 0; q < f; q++)
            {
                for (p = 0; p < f; p++)
                {
                    m = 0;
                    n = 0;

                    for (i = 0; i < f; i++)
                    {
                        for (j = 0; j < f; j++)
                        {
                            b[i, j] = 0;

                            if (i != q && j != p)
                            {
                                b[m, n] = num[i, j];

                                if (n < (f - 2))
                                {
                                    n++;
                                }
                                else
                                {
                                    n = 0;
                                    m++;
                                }
                            }
                        }
                    }

                    fac[q, p] = (double)Math.Pow(-1, q + p) * Determ(b, f - 1);
                    while (fac[q, p] < 0)
                    {
                        fac[q, p] += 26;
                    }
                }
            }

            return fac;
        }

        /// <summary>
        /// Gets the key inverse.
        /// </summary>
        /// <param name="num">The num.</param>
        /// <param name="r">The r.</param>
        /// <returns>inverse key</returns>
        private static int[,] GetKeyInverse(int[,] num, int r)
        {
            double[,] fac = Cofact(num, r);
            int i, j;
            double[,] b = new double[num.GetLength(0), num.GetLength(0)];
            int[,] inv = new int[num.GetLength(0), num.GetLength(0)];
            double d;

            for (i = 0; i < r; i++)
            {
                for (j = 0; j < r; j++)
                {
                    b[i, j] = fac[j, i];
                }
            }

            d = Determ(num, r);

            int oneOverD = EclidInverse((int)d, 26);

            if (oneOverD == 0)
            {
                throw new System.Exception("Invalid Key it has no inverse");
            }

            while (oneOverD < 0)
            {
                oneOverD += 26;
            }

            for (i = 0; i < r; i++)
            {
                for (j = 0; j < r; j++)
                {
                    inv[i, j] = (int)(b[i, j] * oneOverD);

                    // mod 26
                    inv[i, j] %= 26;
                }
            }

            return inv;
        }

        /// <summary>
        /// Multiplies two matricies
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="plain">The plain.</param>
        /// <returns>result of mutiplication</returns>
        private static int[,] MultiplyMat(int[,] key, int[,] plain)
        {
            int matrix1Rows = key.GetLength(0);
            int matrix1Columns = key.GetLength(1);
            int matrix2Columns = plain.GetLength(1);
            int[,] retMat = new int[matrix1Rows, matrix2Columns];

            for (int i = 0; i < matrix1Rows; i++)
            {
                for (int j = 0; j < matrix2Columns; j++)
                {
                    int sum = 0;
                    for (int k = 0; k < matrix1Columns; k++)
                    {
                        sum += key[i, k] * plain[k, j];
                    }

                    retMat[i, j] = sum % 26;
                }
            }

            return retMat;
        }

        /// <summary>
        /// Gets the text.
        /// </summary>
        /// <param name="plain">The plain.</param>
        /// <returns>text as array of int</returns>
        private int[] GetText(string plain)
        {
            int[] charArray = new int[plain.Length];
            for (int i = 0; i < plain.Length; i++)
            {
                charArray[i] = plain[i] - 'a';
            }

            return charArray;
        }

        /// <summary>
        /// Gets the key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>key 2d array</returns>
        private int[,] GetKeyText(string key)
        {
            double sizeDouble = Math.Sqrt(key.Length);
            int sizeInt = (int)sizeDouble;
            if (sizeDouble % (int)sizeInt != 0)
            {
                throw new System.Exception("Invalid Key (Must be squared value to form a squared matrix)");
            }

            // return null;
            int[,] keyMatrix = new int[sizeInt, sizeInt];
            int cnti = 0;
            int cntj = 0;

            for (int i = 0; i < key.Length; i++)
            {
                keyMatrix[cnti, cntj] = key[i] - 'a';
                cntj++;
                if (cntj == sizeInt)
                {
                    cntj = 0;
                    cnti++;
                }
            }

            return keyMatrix;
        }

        /// <summary>
        /// Gets the key number.
        /// </summary>
        /// <param name="keyChar">The key char.</param>
        /// <returns>key as a number</returns>
        private int[,] GetKeyNumber(string keyChar)
        {
            string[] key = keyChar.Split(',');
            if (key.Length == 1)
            {
                throw new Exception("Seperate the numbers by comma");
            }

            double sizeDouble = Math.Sqrt(key.Length);
            int sizeInt = (int)sizeDouble;
            if (sizeDouble % (int)sizeInt != 0)
            {
                throw new System.Exception("Invalid Key (Must be squared value to form a squared matrix)");
            }

            // return null;
            int[,] keyMatrix = new int[sizeInt, sizeInt];
            int cnti = 0;
            int cntj = 0;

            for (int i = 0; i < key.Length; i++)
            {
                keyMatrix[cnti, cntj] = int.Parse(key[i]);
                if (keyMatrix[cnti, cntj] < 0 || keyMatrix[cnti, cntj] > 25)
                {
                    throw new Exception("Value can have values from 0 to 25");
                }

                cntj++;
                if (cntj == sizeInt)
                {
                    cntj = 0;
                    cnti++;
                }
            }

            return keyMatrix;
        }

        /// <summary>
        /// Gets the key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>the key</returns>
        private int[,] GetKey(string key)
        {
            if (this.CheckText(key))
            {
                return this.GetKeyText(key);
            }
            else
            {
                return this.GetKeyNumber(key);
            }
        }

        /// <summary>
        /// Checks the text.
        /// </summary>
        /// <param name="plainText">The plain text.</param>
        /// <returns>checks if the plain is char</returns>
        private bool CheckText(string plainText)
        {
            bool isText = true;
            for (int i = 0; i < plainText.Length; i++)
            {
                if (plainText[i] < 'a' || plainText[i] > 'z')
                {
                    isText = false;
                }
            }

            if (isText)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Gets the numbers.
        /// </summary>
        /// <param name="plainText">The plain text.</param>
        /// <returns>array of numbers</returns>
        private int[] GetNumbers(string plainText)
        {
            try
            {
                string[] temp = plainText.Split(',');
                int[] charArray = new int[temp.Length];
                for (int i = 0; i < temp.Length; i++)
                {
                    charArray[i] = int.Parse(temp[i]);
                    if (charArray[i] < 0 || charArray[i] > 25)
                    {
                        throw new Exception("Value can have values from 0 to 25");
                    }
                }

                return charArray;
            }
            catch
            {
                throw new Exception("string must be numbers seperated by ,");
            }
        }
    }
}
